/* 
 * This file is part of the Minecraft Overviewer.
 *
 * Minecraft Overviewer is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Minecraft Overviewer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with the Overviewer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdarg.h>
#include <string.h>
#include "overviewer.h"

/* this file defines render_primitives,
   a list of all render primitives, ending in NULL
   all of these will be available to the user, so DON'T include primitives
   that are only useful as a base for other primitives.
   
   this file is auto-generated by setup.py */
#include "primitives.h"

/* rendermode encapsulation */

/* helper to create a single primitive */
RenderPrimitive* render_primitive_create(PyObject* prim, RenderState* state) {
    RenderPrimitive* ret = NULL;
    RenderPrimitiveInterface* iface = NULL;
    uint32_t i;
    PyObject* pyname;
    const char* name;

    pyname = PyObject_GetAttrString(prim, "name");
    if (!pyname)
        return NULL;
    name = PyUnicode_AsUTF8(pyname);

    for (i = 0; render_primitives[i] != NULL; i++) {
        if (strcmp(render_primitives[i]->name, name) == 0) {
            iface = render_primitives[i];
            break;
        }
    }
    Py_DECREF(pyname);

    if (iface == NULL)
        return (RenderPrimitive*)PyErr_Format(PyExc_RuntimeError, "invalid primitive name: %s", name);

    ret = calloc(1, sizeof(RenderPrimitive));
    if (ret == NULL) {
        return (RenderPrimitive*)PyErr_Format(PyExc_RuntimeError, "Failed to alloc a render primitive");
    }

    if (iface->data_size > 0) {
        ret->primitive = calloc(1, iface->data_size);
        if (ret->primitive == NULL) {
            free(ret);
            return (RenderPrimitive*)PyErr_Format(PyExc_RuntimeError, "Failed to alloc render primitive data");
        }
    }

    ret->iface = iface;

    if (iface->start) {
        if (iface->start(ret->primitive, state, prim)) {
            free(ret->primitive);
            free(ret);
            return NULL;
        }
    }

    return ret;
}

RenderMode* render_mode_create(PyObject* mode, RenderState* state) {
    RenderMode* ret = NULL;
    PyObject* mode_fast = NULL;
    uint32_t i;

    mode_fast = PySequence_Fast(mode, "Mode is not a sequence type");
    if (!mode_fast)
        return NULL;

    ret = calloc(1, sizeof(RenderMode));
    ret->state = state;
    ret->num_primitives = PySequence_Length(mode);
    ret->primitives = calloc(ret->num_primitives, sizeof(RenderPrimitive*));
    for (i = 0; i < ret->num_primitives; i++) {
        PyObject* pyprim = PySequence_Fast_GET_ITEM(mode_fast, i);
        RenderPrimitive* prim = render_primitive_create(pyprim, state);

        if (!prim) {
            render_mode_destroy(ret);
            Py_DECREF(mode_fast);
            return NULL;
        }

        ret->primitives[i] = prim;
    }

    return ret;
}

void render_mode_destroy(RenderMode* self) {
    uint32_t i;

    for (i = 0; i < self->num_primitives; i++) {
        RenderPrimitive* prim = self->primitives[i];
        /* we may be destroying a half-constructed mode, so we need this
           check */
        if (prim) {
            if (prim->iface->finish) {
                prim->iface->finish(prim->primitive, self->state);
            }
            if (prim->primitive) {
                free(prim->primitive);
            }
            free(prim);
        }
    }
    free(self->primitives);
    free(self);
}

bool render_mode_occluded(RenderMode* self, int32_t x, int32_t y, int32_t z) {
    uint32_t i;
    bool occluded = false;
    for (i = 0; i < self->num_primitives; i++) {
        RenderPrimitive* prim = self->primitives[i];
        if (prim->iface->occluded) {
            occluded |= prim->iface->occluded(prim->primitive, self->state, x, y, z);
        }

        if (occluded)
            return occluded;
    }
    return occluded;
}

bool render_mode_hidden(RenderMode* self, int32_t x, int32_t y, int32_t z) {
    uint32_t i;
    bool hidden = false;
    for (i = 0; i < self->num_primitives; i++) {
        RenderPrimitive* prim = self->primitives[i];
        if (prim->iface->hidden) {
            hidden |= prim->iface->hidden(prim->primitive, self->state, x, y, z);
        }

        if (hidden)
            return hidden;
    }
    return hidden;
}

void render_mode_draw(RenderMode* self, PyObject* img, PyObject* mask, PyObject* mask_light) {
    uint32_t i;
    for (i = 0; i < self->num_primitives; i++) {
        RenderPrimitive* prim = self->primitives[i];
        if (prim->iface->draw) {
            prim->iface->draw(prim->primitive, self->state, img, mask, mask_light);
        }
    }
}

/* options parse helper */
bool render_mode_parse_option(PyObject* support, const char* name, const char* format, ...) {
    va_list ap;
    PyObject *item, *dict;
    bool ret;

    if (support == NULL || name == NULL)
        return false;

    dict = PyObject_GetAttrString(support, "option_values");
    if (!dict)
        return false;

    item = PyDict_GetItemString(dict, name);
    if (item == NULL) {
        Py_DECREF(dict);
        return false;
    };

    /* make sure the item we're parsing is a tuple
       for VaParse to work correctly */
    if (!PyTuple_Check(item)) {
        item = PyTuple_Pack(1, item);
    } else {
        Py_INCREF(item);
    }

    va_start(ap, format);
    ret = PyArg_VaParse(item, format, ap);
    va_end(ap);

    Py_DECREF(item);
    Py_DECREF(dict);

    if (!ret) {
        PyObject *errtype, *errvalue, *errtraceback, *errstring;

        PyErr_Fetch(&errtype, &errvalue, &errtraceback);
        errstring = PyUnicode_AsUTF8String(errvalue);

        PyErr_Format(PyExc_TypeError, "rendermode option \"%s\" has incorrect type (%s)", name, errstring);

        Py_DECREF(errtype);
        Py_DECREF(errvalue);
        Py_XDECREF(errtraceback);
    }

    return ret;
}
